Skip to content

chore(tailwind): upgrade to v4 and swap clsx/tailwind-merge for cn - #7419

Open
waleedlatif1 wants to merge 4 commits into
stagingfrom
worktree-shadcn-cn-migrate
Open

chore(tailwind): upgrade to v4 and swap clsx/tailwind-merge for cn#7419
waleedlatif1 wants to merge 4 commits into
stagingfrom
worktree-shadcn-cn-migrate

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Upgrade apps/sim from Tailwind v3.4 to v4.3 — CSS-first config, tailwind.config.ts deleted
  • Replace clsx + tailwind-merge with the cn package in @sim/emcn and apps/docs
  • Intended to be visually inert; every claim below was verified by building the stylesheet both ways and diffing computed styles in a headless browser, not by reading the upgrade guide

Parity evidence

check result
Preflight (bare elements, all computed properties) 112/112 identical
Utilities the app uses, pixel-diffed identical
Border-width combinations (border border-b-0, border border-2, …) 10/10 identical
Changed class strings through both merge engines 699 strings, 0 diffs
cn vs tailwind-merge v3 over harvested class pairs 427,034 pairs, 0 diffs
Precompiled cn tables vs runtime-compiled 427,040 inputs, 0 diffs

v4 defaults deliberately pinned

Taking these would have changed rendering:

  • --font-sans — v4 changed its own default to an -apple-system stack, and Preflight resolves the document font through it
  • --text-xs--line-heightxs is the one custom size that collides with Tailwind's scale and inherits its ratio (16.5px → 14.67px)
  • The 24 stock palette colours the app uses — v4 re-authored the palette in oklch, which is visibly more saturated on P3 displays. Opacity modifiers verified pixel-identical afterwards.

Two things worth reviewer attention

  • borderWidth.DEFAULT has no v4 theme key. A PostCSS pass (lib/postcss/hairline-border-width.mjs) rewrites Tailwind's own 1px output through --border-width in place. Re-declaring the utilities in a trailing @layer — the obvious fix — makes border beat border-2 and border-t-0, because it lands after Tailwind's output at equal specificity. The plugin preserves Tailwind's ordering, specificity and variant coverage.
  • rounded-sm / rounded-md are NOT renamed. @tailwindcss/upgrade would rewrite all 146 sites to rounded-xs; that is wrong here because this app overrides --radius-sm/--radius-md. Verified identical as-is.

Preflight compatibility

v4 drops or changes seven v3 Preflight behaviours that no diff would show. Restored: button cursor: pointer (plus v3's :disabled companion), form-control background, ::placeholder colour, <th>/<td>/<option> padding, [type=search] normalisation, <dialog> centring.

Bundle

cn is compiled ahead of time (cn build --full) so its config compiler stays out of the browser. @sim/emcn goes 7,037 → 11,033 B gzip (+4.0 KB) for a ~30× faster merger with no first-call compile; using cn/config instead would have been +9.1 KB and ~3.7 ms of hydration work. check:cn-tables fails CI if the tables drift from cn.config.mjs.

Known behaviour difference

note-block-view.tsx has focus-visible:outline-2 focus-visible:outline-current/50 with no outline class. In v3 that drew nothing (outline-<n> set width only); in v4 it also sets outline-style: solid, so a keyboard-focus ring now appears on the two note edit buttons. Left as-is — it is the code's plain intent and keyboard-only. Say the word and I'll drop the dead classes to preserve v3 exactly.

Supply chain

cn is added to minimumReleaseAgeExcludes. Its npm name was recycled in 2026, so every usable version is younger than the 7-day gate and it can never pass on its own. Pinned to an exact version in both consumers; bun.lock carries the integrity hash and CI installs --frozen-lockfile. The exclude can be removed in a follow-up now that the lockfile entry exists.

Type of Change

  • Chore (dependency/tooling upgrade)

Testing

  • Full suite: 41,614 tests passing, 19/19 tasks
  • bun run check:audits: 46/46 (includes the new check:cn-tables)
  • bunx turbo run type-check: 26/26 · bun run lint:check: 26/26
  • Production builds of apps/sim and apps/docs: clean; hairline plugin verified present in shipped CSS

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

🤖 Generated with Claude Code

Migrates apps/sim from Tailwind v3.4 to v4.3 (CSS-first config) and replaces
clsx + tailwind-merge with the cn package in @sim/emcn and apps/docs.

The upgrade is intended to be visually inert. Parity was verified by building
the stylesheet both ways and diffing computed styles in a real browser:

- Preflight: 112/112 bare elements identical, after restoring seven v3
  behaviours v4 dropped (button cursor, form-control background, placeholder
  colour, table/option padding, search-field normalisation, dialog centring)
- Utilities: every class the app uses renders pixel-identically
- Border widths: 10/10 combinations identical
- Class strings: 699 changed strings run through both merge engines, 0 diffs

Pinned against v4 defaults that would otherwise shift rendering: --font-sans
(v4 changed its own default stack), --text-xs--line-height, and the 24 stock
palette colours the app uses (v4 re-authored the palette in oklch, which is
visibly more saturated on P3 displays).

borderWidth.DEFAULT has no v4 theme key, so a PostCSS pass rewrites Tailwind's
own border-width output through --border-width. Re-declaring the utilities in a
trailing @layer instead would make `border` beat `border-2` and `border-t-0`.

rounded-sm and rounded-md are deliberately NOT renamed: this app overrides that
radius scale, so the standard v3->v4 rename would change their values.

cn is compiled ahead of time (cn build --full) so the config compiler stays out
of the browser bundle; check:cn-tables fails CI if the tables drift.
@waleedlatif1
waleedlatif1 requested a review from a team as a code owner September 2, 2026 21:58
@vercel

vercel Bot commented Sep 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs Ready Ready Preview Sep 2, 2026 10:43pm UTC

Request Review

…-migrate

# Conflicts:
#	apps/sim/app/workspace/[workspaceId]/home/components/suggested-actions/suggested-actions.tsx
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile-apps

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@greptile-apps

greptile-apps Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR migrates the Sim application to Tailwind CSS v4 while preserving the existing theme and compatibility behavior, and replaces the existing class-merging stack with precompiled cn tables.

  • Moves Tailwind configuration into CSS-first theme, source, variant, and compatibility declarations.
  • Adds a PostCSS transformation preserving the existing high-density hairline border behavior.
  • Replaces clsx and tailwind-merge with a shared precompiled cn implementation and adds a table-drift check.
  • Updates affected utility names across application, documentation, and shared UI components.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains.

No blocking failure remains.

Important Files Changed

Filename Overview
apps/sim/app/_styles/globals.css Defines the Tailwind v4 CSS-first sources, theme values, variants, compatibility styles, and pinned v3 defaults.
apps/sim/lib/postcss/hairline-border-width.mjs Rewrites Tailwind hairline border declarations after compilation while preserving generated selector ordering.
apps/sim/postcss.config.mjs Runs Tailwind’s PostCSS integration before the hairline compatibility transformation.
packages/emcn/src/lib/cn.ts Exposes the shared class merger backed by precompiled lookup tables.
packages/emcn/cn.config.mjs Extends class conflict handling for the repository’s custom typography scale.
scripts/check-cn-tables.ts Regenerates and compares class-merger tables to prevent committed configuration drift.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Sources[Application and package class names] --> Scan[Tailwind v4 source scanning]
  Theme[CSS-first theme and variants] --> Build[Tailwind PostCSS compilation]
  Scan --> Build
  Build --> Hairline[Hairline border-width rewrite]
  Hairline --> CSS[Shipped application stylesheet]
  Config[cn.config.mjs] --> Tables[Precompiled cn tables]
  Tables --> Merge[Shared cn runtime]
  Merge --> Sources
  Check[check:cn-tables] --> Tables
Loading

Reviews (3): Last reviewed commit: "fix(tailwind): close the remaining v3/v4..." | Re-trigger Greptile

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 278 files

Confidence score: 4/5

  • .agents/skills/design-taste-frontend/SKILL.md still contains canonical examples importing motion/react, so copied samples can fail with an unresolved motion module; replace every sample import with the repository’s declared dependency.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".agents/skills/design-taste-frontend/SKILL.md">

<violation number="1" location=".agents/skills/design-taste-frontend/SKILL.md:7">
P2: The repo override still leaves canonical samples importing `motion/react`, so copying any of those examples into this repo can fail with an unresolved `motion` module. Replace every sample import with the declared `framer-motion` import.</violation>
</file>

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.

Fix all with cubic | Re-trigger cubic

Comment thread .agents/skills/design-taste-frontend/SKILL.md
Comment thread .gitignore Outdated
Was an unanchored `.cn-check/`, which would ignore a directory of that name
anywhere in the tree; the check script only ever creates it under
packages/emcn.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile-apps

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 278 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.

Re-trigger cubic

Found by rebuilding the v3 stylesheet from this same tree and diffing computed
styles for all 4,674 class strings the app actually uses — combinations, not
just individual utilities, which is where these only show up.

- leading-*/text-* precedence: v4 routes a text-* utility's line-height through
  --tw-leading, so a leading-* class now wins at every breakpoint. v3 let a
  responsive text-lg reassert its own 28px leading; restate that explicitly.
- overflow-wrap: v4 emits the arbitrary property before `break-words`, flipping
  the winner from `anywhere` to `break-word`. Drop the redundant utility.
- z-9999999 and duration-[30ms] never took effect under v3 (outside the
  generated scale; rejected as ambiguous). v4 honours both, which would change
  stacking order and a transition from 150ms to 30ms. Removed.
- note-block focus ring: outline-<n> set width only in v3, so this drew
  nothing; v4 also sets outline-style. Removed to keep v3 rendering.

Every remaining computed-style difference is now verified non-visual: colour
notation (oklch/oklab, pixel-identical), gradient interpolation, mask-composite
keyword aliasing, and outline-none vs outline-hidden.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile-apps

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 280 files

Confidence score: 3/5

  • In wand-prompt-bar.tsx, removing the high stacking layer can let positioned editor content cover the prompt bar and intercept clicks; preserve the overlay with a valid v4 z-index utility.
  • In action-bar.tsx, removing data-action-menu-ready makes the bar retract at Tailwind’s 150ms default instead of 30ms, creating a noticeably slower interaction; keep duration-[30ms] on the base class.
  • In content-post-page.tsx, sm:leading-7 changes the description’s wider-screen line-height from the stated 150% value, causing a small visual regression; remove the override or retain the prior 150% setting.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/sim/app/(landing)/components/content-post-page/content-post-page.tsx">

<violation number="1" location="apps/sim/app/(landing)/components/content-post-page/content-post-page.tsx:82">
P3: At `sm` and wider, `sm:leading-7` overrides the existing `leading-[150%]`, changing the 18px description from 27px to 28px line-height. Remove this override or use the previous 150% value to preserve the stated visual parity.</violation>
</file>

<file name="apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx">

<violation number="1" location="apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx:425">
P2: When `data-action-menu-ready` is removed, this element loses the base 30ms duration and falls back to Tailwind’s 150ms default, making the action bar retract five times slower. Keep `duration-[30ms]` on the base class while retaining the `duration-100` ready override.</violation>
</file>

<file name="apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/wand-prompt-bar/wand-prompt-bar.tsx">

<violation number="1" location="apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/wand-prompt-bar/wand-prompt-bar.tsx:83">
P2: When the prompt bar overlaps the editor, removing its high stacking layer leaves it at `z-index: auto`, so later positioned editor content can cover the bar and intercept clicks. Preserve the layer with a valid v4 utility or the appropriate shared z-index token.</violation>
</file>

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.

Fix all with cubic | Re-trigger cubic

'relative flex flex-row items-center gap-[2px]',
isSwell && [
'pointer-events-none h-full opacity-0 transition-opacity duration-[30ms] [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]',
'pointer-events-none h-full opacity-0 transition-opacity [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]',

@cubic-dev-ai cubic-dev-ai Bot Sep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When data-action-menu-ready is removed, this element loses the base 30ms duration and falls back to Tailwind’s 150ms default, making the action bar retract five times slower. Keep duration-[30ms] on the base class while retaining the duration-100 ready override.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx, line 425:

<comment>When `data-action-menu-ready` is removed, this element loses the base 30ms duration and falls back to Tailwind’s 150ms default, making the action bar retract five times slower. Keep `duration-[30ms]` on the base class while retaining the `duration-100` ready override.</comment>

<file context>
@@ -422,7 +422,7 @@ export const ActionBar = memo(
             'relative flex flex-row items-center gap-[2px]',
             isSwell && [
-              'pointer-events-none h-full opacity-0 transition-opacity duration-[30ms] [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]',
+              'pointer-events-none h-full opacity-0 transition-opacity [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]',
               'group-data-[action-menu-ready]:pointer-events-auto group-data-[action-menu-ready]:opacity-100 group-data-[action-menu-ready]:duration-100',
             ]
</file context>
Suggested change
'pointer-events-none h-full opacity-0 transition-opacity [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]',
'pointer-events-none h-full opacity-0 transition-opacity duration-[30ms] [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]',
Fix with cubic

'-translate-y-3 absolute right-0 bottom-full left-0 gap-2',
'rounded-lg border bg-background shadow-lg',
'z-9999999 transition-all duration-150',
'transition-all duration-150',

@cubic-dev-ai cubic-dev-ai Bot Sep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When the prompt bar overlaps the editor, removing its high stacking layer leaves it at z-index: auto, so later positioned editor content can cover the bar and intercept clicks. Preserve the layer with a valid v4 utility or the appropriate shared z-index token.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/wand-prompt-bar/wand-prompt-bar.tsx, line 83:

<comment>When the prompt bar overlaps the editor, removing its high stacking layer leaves it at `z-index: auto`, so later positioned editor content can cover the bar and intercept clicks. Preserve the layer with a valid v4 utility or the appropriate shared z-index token.</comment>

<file context>
@@ -80,7 +80,7 @@ export function WandPromptBar({
         '-translate-y-3 absolute right-0 bottom-full left-0 gap-2',
         'rounded-lg border bg-background shadow-lg',
-        'z-9999999 transition-all duration-150',
+        'transition-all duration-150',
         isExiting ? 'opacity-0' : 'opacity-100',
         className
</file context>
Suggested change
'transition-all duration-150',
'z-[9999999] transition-all duration-150',
Fix with cubic

</h1>
<p
className='mt-4 text-[var(--text-body)] text-base leading-[150%] tracking-[0.02em] sm:text-lg'
className='mt-4 text-[var(--text-body)] text-base leading-[150%] tracking-[0.02em] sm:text-lg sm:leading-7'

@cubic-dev-ai cubic-dev-ai Bot Sep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: At sm and wider, sm:leading-7 overrides the existing leading-[150%], changing the 18px description from 27px to 28px line-height. Remove this override or use the previous 150% value to preserve the stated visual parity.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/app/(landing)/components/content-post-page/content-post-page.tsx, line 82:

<comment>At `sm` and wider, `sm:leading-7` overrides the existing `leading-[150%]`, changing the 18px description from 27px to 28px line-height. Remove this override or use the previous 150% value to preserve the stated visual parity.</comment>

<file context>
@@ -79,7 +79,7 @@ export function ContentPostPage({
               </h1>
               <p
-                className='mt-4 text-[var(--text-body)] text-base leading-[150%] tracking-[0.02em] sm:text-lg'
+                className='mt-4 text-[var(--text-body)] text-base leading-[150%] tracking-[0.02em] sm:text-lg sm:leading-7'
                 itemProp='description'
               >
</file context>
Suggested change
className='mt-4 text-[var(--text-body)] text-base leading-[150%] tracking-[0.02em] sm:text-lg sm:leading-7'
className='mt-4 text-[var(--text-body)] text-base leading-[150%] tracking-[0.02em] sm:text-lg'
Fix with cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant